﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved All Rights Reserved All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ServiceModel;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Data;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using System.ComponentModel;
using System.Xaml;
using System.Dynamic;
using System.Reflection;
using System.Threading;
using System.Collections.Specialized;
using FinPlusCommon;
using u = FinPlusUtility.Utility;
using log = Logger.Logger;

namespace FinPlusWCFConnData
{
    public class ServiceConn
    {
        public FuncDynamics DynFuncs { get; private set; }
        public IFinPlusFunctions RegFuncs { get; private set; }
        public string Guid { get; private set; }
        public bool HasCompleted { get; set; }

        private enum Params { FunctionName, Id }
        private IFunctionService _funcService;
        private readonly InstanceContext _context;
        private readonly RequestCallback _callback;
        private NetTcpBinding _binding;
        private readonly string _config, _user, _serviceKey, _serviceType, _parms;
        private readonly bool _isTransposed, _isAsync;
        private int _asyncWait, _asyncRetry;
        private readonly u.Env _env;

        //contruct
        public ServiceConn(u.Env env, string config, string user, string serviceKey, string serviceType, string parms, bool isTransposed, bool isAsync)
        {
            _env = env;
            _config = config;
            _user = user;
            _serviceKey = serviceKey;
            _serviceType = serviceType;
            _parms = parms;
            _isTransposed = isTransposed;
            _isAsync = isAsync;
            _callback = new RequestCallback(this);
            _context = new InstanceContext(_callback);
            Guid = u.Guid();

            Initialise();
        }

        public ServiceConn(string xmlPath, string node, bool isTransposed)
        {
            _isTransposed = isTransposed;
            XmlLoad(xmlPath, node);
        }

        //public
        public void GetFunctions()
        {
            try
            {
                _funcService.GetFunctions(_env, _user, _config, _serviceKey, _serviceType, _parms, Guid);
                if (!_isAsync)
                    WaitCompleted();
            }
            catch (FaultException<IFunctionService> e)
            {
                log.Log(Logger.Level.Error, e.Message, e);
            }
        }

        public void SaveFunction(IFinPlusFunction func, ChangeType changeType)
        {
            try
            {
                _funcService.SaveFunction(_env, _user, _config, _serviceKey, func.Cast<Item>(), changeType, Guid);
            }
            catch (FaultException<IFunctionService> e)
            {
                log.Log(Logger.Level.Error, e.Message, e);
            }
        }

        public void Dispose()
        {
            try
            {
                if(_funcService != null)
                    _funcService.Dispose(_env, _user, _config, _serviceKey, Guid);
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, e.Message, e);
            }
        }

        public void Update(int functionId, IFinPlusItem item, ChangeType changeType)
        {
            try
            {
                if (_isTransposed)
                {
                    IFinPlusFuncDynamic dynFunc;
                    if (DynFuncs.TryGet(functionId.ToString(), out dynFunc))
                        if (dynFunc.GetTimeStamp(item.Name) < item.TimeStamp)
                            dynFunc.SetItem(item);
                }
                else if (RegFuncs.ContainsKey(functionId))
                {
                    var regFunc = RegFuncs[functionId];
                    var i = regFunc.Get(item.Name);
                    if (i == null)
                    {
                        regFunc.TryAdd(item);
                        item.Set(item.Value, item.TimeStamp, item.User);
                    }
                    else if (i.TimeStamp < item.TimeStamp)
                        i.Set(item.Value, item.TimeStamp, item.User);
                }
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, e.Message);
            }
        }

        public void Remove(int functionId)
        {
            IFinPlusFunction f = null;
            if (_isTransposed)
                DynFuncs.Remove(functionId.ToString());
            else if (RegFuncs.ContainsKey(functionId))
                RegFuncs.TryRemove(functionId, out f);
        }

        public void Add(IFinPlusFunction func)
        {
            if (_isTransposed)
            {
                var dynFunc = new FuncDynamic(func) { ServiceKey = _serviceKey, User = _user };
                DynFuncs.Add(dynFunc);
                dynFunc.PropertyChanged += Item_Updated;
            }
            else
            {
                func.ServiceKey = _serviceKey;
                RegFuncs[func.Id] = func;
                func.ItemUpdate += Item_Updated;
            }
        }

        //private
        private void Initialise()
        {
            u.Env env;
            var c = u.ConfigXml(u.GetConfigPath(Assembly.GetExecutingAssembly()), out env);
            var conn = c["connections"];

            if (_isAsync)
            {
                _asyncWait = (int)(double.Parse(conn["asyncwait"]) * 1000);
                _asyncRetry = int.Parse(conn["asyncretry"]);
            }

            _binding = new NetTcpBinding()
            {
                ReceiveTimeout = TimeSpan.FromHours(double.Parse(conn["receivetimeout"].ToString())),
                OpenTimeout = TimeSpan.FromHours(double.Parse(conn["opentimeout"])),
                CloseTimeout = TimeSpan.FromHours(double.Parse(conn["closetimeout"])),
                SendTimeout = TimeSpan.FromHours(double.Parse(conn["sendtimeout"]))
            };

            var factory = new DuplexChannelFactory<IFunctionService>(_context, _binding, conn["baseaddress"]);
            _funcService = factory.CreateChannel();

            if (_isTransposed)
            {
                DynFuncs = new FuncDynamics();
            }
            else
            {
                RegFuncs = new Functions();
                RegFuncs.FuncUpdate += Func_Updated;
            }
        }

        private void WaitCompleted()
        {
            for (int x = 0; x < _asyncRetry; x++)
            {
                Thread.Sleep(_asyncWait);
                if (!HasCompleted)
                    return;
            }
        }

        private void XmlLoad(string xmlPath, string node)
        {
            var funcs = new Functions(xmlPath, node);

            if (_isTransposed)
            {
                foreach (var func in funcs.Values)
                    DynFuncs.Add(new FuncDynamic(func));
            }
            else
            {
                RegFuncs = funcs;
            }
        }

        private void Func_Updated(object sender, ItemEventArgs a)
        {
            var func = (Function)sender;

            func.ServiceKey = _serviceKey;

            if(a.Item == null)
                SaveFunction(func, a.ChangeType);
         }

        private void Item_Updated(object sender, ItemEventArgs a)
        {
            var func = (Function)sender;

            _funcService.AlterFunction(_env, _user, _config, func.ServiceKey, a.Item.TimeStamp, func.Name, func.Id, a.Item.Name, a.Item.Value);
        }

        private void Item_Updated(object sender, PropertyChangedEventArgs a)
        {
            var dynFunc = (FuncDynamic)sender;
            object functionName, id, value;
            if (dynFunc.TryGetObject(Params.FunctionName.ToString(), out functionName) && dynFunc.TryGetObject(Params.Id.ToString(), out id) && dynFunc.TryGetObject(a.PropertyName, out value))
                _funcService.AlterFunction(_env, _user, _config, dynFunc.ServiceKey, dynFunc.GetTimeStamp(a.PropertyName), (string)functionName, (int)id, a.PropertyName, value);
        }

        //callback
        [CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
        internal class RequestCallback : IFunctionCallback
        {
            private ServiceConn _serviceConn;

            public  RequestCallback(ServiceConn serviceConn)
            {
                _serviceConn = serviceConn;
            }

            public void DispatchFunction(u.Env env, string serviceKey, string guid, IEnumerable<Item> func, ChangeType changeType)
            {
                try
                {
                    if (!guid.Equals(_serviceConn.Guid)) throw new Exception();

                    var f = new Function(func);

                    if (changeType == ChangeType.Removed)
                        _serviceConn.Remove(f.Id);
                    else if (changeType == ChangeType.New)
                        _serviceConn.Add(f);
                }
                catch (Exception e)
                {
                    log.Log(Logger.Level.Error, e.Message);
                    throw new FaultException(e.Message);
                }
            }

            public void DispatchItem(u.Env env, string serviceKey, string guid, int id, Item item, ChangeType changeType)
            {
                try
                {
                    if (!guid.Equals(_serviceConn.Guid)) throw new Exception();

                     _serviceConn.Update(id, item, changeType);
                }
                catch (Exception e)
                {
                    log.Log(Logger.Level.Error, e.Message);
                    throw new FaultException(e.Message);
                }
            }

            public void DispatchCompleted(u.Env env, string serviceKey, string guid)
            {
                try
                {
                    if (!guid.Equals(_serviceConn.Guid)) throw new Exception();

                    if (_serviceConn.Guid.Equals(guid))
                        _serviceConn.HasCompleted = true;
                }
                catch (Exception e)
                {
                    log.Log(Logger.Level.Error, e.Message);
                    throw new FaultException(e.Message);
                }
            }
        }
    }
}
